/**
 * 
 */
package com.wubo.sec.service.impl;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.wubo.common.util.Assert;
import com.wubo.common.util.CollectionUtils;
import com.wubo.common.util.ObjectUtils;
import com.wubo.sec.SecurityManager;
import com.wubo.sec.config.LangUtil;
import com.wubo.sec.event.MenuEvent;
import com.wubo.sec.log.LogHelper;
import com.wubo.sec.model.Authority;
import com.wubo.sec.model.Menu;
import com.wubo.sec.service.IMenuService;
import com.wubo.sec.util.ObjectDifferenceChecker;
import com.wubo.sec.util.UUID;
import com.wubo.spring.hibernate.BaseHibernateDao;

/**
 * @author wubo
 * @CreateDate 2010-8-3
 * @version 1.0.01
 */
@SuppressWarnings("unchecked")
public class MenuService implements IMenuService {
	private static Log logger = LogFactory.getLog(MenuService.class);
	
	private BaseHibernateDao dao;
	
	public MenuService(BaseHibernateDao dao){
		this.dao = dao;
	}
	
	public List<String> listName(String name){
		return dao.find("select m.name from Menu m where m.name like '%"+name+"%'");
	}
	
	/* (non-Javadoc)
	 * @see com.asc.sf.service.IMenuService#create(com.asc.sf.entity.Menu)
	 */
	public String create(Menu menu) {
		Assert.notNull(menu);
		Assert.hasText(menu.getName());
		menu.setCode(UUID.uuid());
		
		if(menu.getParentCode() == null){
			menu.setGrade(1);
		}else{
			Menu parent = get(menu.getParentCode());
			if(parent.isLeaf()){//如果父菜单是叶子，那么现在...（自然升级了）
				dao.bulkUpdate("update Menu m set m.leaf = ? where m.code = ?", new Object[]{false, parent.getCode()});
			}
			menu.setGrade(parent.getGrade() + 1);
		}
		if(menu.getOrderIndex() < 1){ //顺序索引小于1则自动为最后一个
			menu.setOrderIndex( getMaxOrderIndex(menu.getParentCode()) + 1 );
		}else{ //当指定索引时，自动检查索引是否重复， 如检测到重复则重排所有兄弟菜单的索引
			refreshOrderIndex(menu.getParentCode(), menu.getOrderIndex());
		}
		menu.setLeaf(true);

		java.util.Date now = new java.util.Date();
		String username = SecurityManager.getCurrentUser().getUsername();
		menu.setCreateTime(now);
		menu.setCreateUser(username);
		menu.setUpdateTime(now);
		menu.setUpdateUser(username);
		
		if(logger.isDebugEnabled()){
			logger.debug("Create a Menu : "+ObjectUtils.objToString(menu));
		}
		
		onAdd(menu);
		
		String code = (String) dao.save(menu);
		
		afterAdd(menu);
		
		return code;
	}
	
	//重排菜单同级索引
	private void refreshOrderIndex(String parentCode, int orderIndex){
		Integer _index = dao.queryForObject("select m.orderIndex from Menu m where m.orderIndex = "+orderIndex+" and m.parentCode "+(parentCode != null ? "='"+parentCode+"'" : "is null"));
		if(_index != null && orderIndex == _index){ //检查是否存在索引重复
			List<String> menuCodes = dao.find("select m.code from Menu m where m.parentCode "+
					(parentCode != null ? "='"+parentCode+"'" : "is null")+" order by m.orderIndex");
			for(int i=0; i<menuCodes.size(); i++){
				String menuCode = menuCodes.get(i);
				int index = i+1;
				if(index >= orderIndex){
					index += 1;
				}
				dao.bulkUpdate("update Menu m set m.orderIndex = ? where m.code = ?", index, menuCode);
			}
		}
	}

	/* (non-Javadoc)
	 * @see com.asc.sf.service.IMenuService#delete(com.asc.sf.entity.Menu)
	 */
	public void delete(Menu menu) {
		Assert.notNull(menu);
		
		//删除菜单，其子菜单自动上升一级
		List<String> subMenuCodes = dao.find("select m.code from Menu m where m.parentCode = ?", new Object[]{menu.getCode()});
		if(subMenuCodes != null && subMenuCodes.size() > 0){
			int orderIndex = getMaxOrderIndex(menu.getParentCode());
			for(int i=0; i<subMenuCodes.size(); i++){
				String subCode = subMenuCodes.get(i);
				dao.bulkUpdate("update Menu m set m.parentCode = ?, m.grade = ?, m.orderIndex = " + (orderIndex++) + " where m.code = ?"
						,menu.getParentCode(), menu.getGrade(), subCode);
				updateGrade(subCode, 2);
			}
		}

		onDelete(menu);
		
		dao.delete(menu);
		
		afterDelete(menu);
	}

	/* (non-Javadoc)
	 * @see com.asc.sf.service.IMenuService#delete(java.lang.String[])
	 */
	public void delete(String[] codes) {
		Assert.notNull(codes);
		Assert.isTrue(codes.length > 0);
		
		for(int i=0; i<codes.length; i++){
			delete(get(codes[i]));
		}
	}

	/* (non-Javadoc)
	 * @see com.asc.sf.service.IMenuService#findAll()
	 */
	public List<Menu> findAll(String where, Object... params) {
		if(StringUtils.isEmpty(where)){
			return findAllMenus();
		}else{
			return dao.find("select m from Menu m "+where, params);
		}
	}
	
	public Set<Menu> findAllTreeRunAs(){
		return findAllTree(false);
	}
	
	public Set<Menu> findAllTree(boolean onlyActive) {
		List<Menu> menus = onlyActive ? findAllActiveMenus() : findAllMenus();
		
		Map<String, Menu> tempMap = new HashMap<String, Menu>(menus.size());
		
		for(int i=0; i<menus.size(); i++){
			Menu menu = menus.get(i);
			tempMap.put(menu.getCode(), menu);
		}
		
		Set<Menu> rootMenus = new TreeSet<Menu>(); 
		
		for(int i=0; i<menus.size(); i++){
			Menu menu = menus.get(i);
			String code = menu.getParentCode();
			Menu tempMenu = tempMap.get(code);
			if(tempMenu != null){
				tempMenu.initSubMenus();
				tempMenu.getSubMenus().add(menu);
			}else{
				rootMenus.add(menu);
			}
		}
		return rootMenus;
	}
	
	private List<Menu> findAllMenus(){
		return dao.find("from Menu m");
	}
	
	private List<Menu> findAllActiveMenus(){
		return dao.find("from Menu m where active = ?", true);
	}

	/* (non-Javadoc)
	 * @see com.asc.sf.service.IMenuService#getMenuTree(com.asc.sf.entity.Authority[])
	 */
	public Set<Menu> getMenuTree(Authority[] authorities) {
		if(authorities == null || authorities.length < 1){
			return new java.util.HashSet<Menu>(0);
		}
		StringBuilder sb = new StringBuilder();
		for(int i=0; i<authorities.length; i++){
			if(i>0)sb.append(",");
			sb.append("'"+authorities[i].getCode()+"'");
		}

		return getMenuTree(sb.toString());
	}
	
	public Set<Menu> getMenuTree(String[] authorityCodes) {
		if(authorityCodes == null || authorityCodes.length < 1){
			return new java.util.HashSet<Menu>(0);
		}
		StringBuilder sb = new StringBuilder();
		for(int i=0; i<authorityCodes.length; i++){
			if(i>0)sb.append(",");
			sb.append("'"+authorityCodes[i]+"'");
		}
		
		return getMenuTree(sb.toString());
	}
	
	private Set<Menu> getMenuTree(String authorityCodes) {
		List<Menu> menus = dao.find("select m from Menu m inner join m.auth a where a.code in ("+authorityCodes.toString()+") and m.active = ?", true); 
		
		Set<Menu> rootMenus = new TreeSet<Menu>();
		
		List<Menu> allMenus = findAllActiveMenus();
		
		Map<String, Menu> tempMap = new HashMap<String, Menu>(menus.size());
		
		for(int i=0; i<allMenus.size(); i++){
			Menu menu = allMenus.get(i);
			tempMap.put(menu.getCode(), menu);
		}
		
		for(int i=0; i<allMenus.size(); i++){
			Menu menu = allMenus.get(i);
			if(menus.contains(menu)){
				putInSet(menu, rootMenus, tempMap);
			}
		}
		
		return rootMenus;
	}
	
	private void putInSet(Menu menu, Set<Menu> rootMenus, Map<String, Menu> tempMap){
		if(tempMap.containsKey(menu.getParentCode())){
			Menu parentMenu = tempMap.get(menu.getParentCode());
			parentMenu.initSubMenus();
			parentMenu.getSubMenus().add(menu);
			
			putInSet(parentMenu, rootMenus, tempMap);
		}else{
			if(! rootMenus.contains(menu) ){
				rootMenus.add(menu);
			}
		}
	}

	/* (non-Javadoc)
	 * @see com.asc.sf.service.IMenuService#getSplitPage(int, int)
	 */
	public List<Menu> getSplitPage(int start, int limit, String where, Object... params) {
		return dao.queryForPage(start, limit, "select m from Menu m "+where, params);
	}
	
	public long getTotal(String where, Object... params) {
		Long result = dao.queryForObject("select count(m.code) from Menu m "+where, params); 
		return result;
	}
	
	public void updateStruct(String code, String originParentCode, String newParentCode, int nodeIndex){
		Assert.hasText(code);
		
		logger.debug("Update Tree Struct. Code:"+code+",OriginParentCode:"+originParentCode+",newParentCode:"+newParentCode+",NodeIndex:"+nodeIndex);
		
		if(originParentCode == newParentCode){
			dao.bulkUpdate("update Menu m set m.orderIndex = ? where m.code = ?", nodeIndex, code);
			refreshOrderIndex(originParentCode, nodeIndex);

			String menuname = dao.queryForObject("select m.name from Menu m where m.code = ?", code);
			
			LogHelper.actionLog(LangUtil.get("UpdateMenuStruct"), 
					LangUtil.get("UpdateMenuStructDetail", code+"("+menuname+")"));
		}else{
			Menu menu = get(code);
			menu.setParentCode(newParentCode);
			menu.setOrderIndex(nodeIndex);
			
			update(menu);
			dao.flush();
		}
		
	}
	
	public void saveStruct(List<Map<String, ?>> list){
		Assert.notNull(list);
		
		iteratorMenuList(list, null, 1);
		
		LogHelper.actionLog(LangUtil.get("UpdateMenuStruct"), LangUtil.get("UpdateMenuStruct"));
	}
	//迭代菜单树（辅助方法）
	private void iteratorMenuList(List<Map<String, ?>> menus, String parentCode, int grade){
		
		for(int i=0; i<menus.size(); i++){
			Map<String, ?> menu = menus.get(i);
			String code = (String) menu.get("code");
			List<Map<String, ?>> subMenus = (List<Map<String, ?>>) menu.get("subMenus");
			boolean leaf = true;
			if(subMenus != null){
				iteratorMenuList(subMenus, code, grade + 1);
				leaf = false;
			}
			doUpdateMenuStruct(code, parentCode, grade, i+1, leaf);
		}
	}
	//更新菜单的树层级结构
	private void doUpdateMenuStruct(String code, String parentCode, int grade, int orderIndex, boolean leaf){
		dao.executeSQL("update sec_menu m set parentCode = ?, grade = ?, orderIndex = ?, leaf = ? where code = ?",
				parentCode, grade, orderIndex, leaf, code);
	}

	/* (non-Javadoc)
	 * @see com.asc.sf.service.IMenuService#update(com.asc.sf.entity.Menu)
	 */
	public Menu update(Menu menu) {
		Assert.notNull(menu);
		
		Menu temp = get(menu.getCode());
		
		Menu old = temp.clone();
		
		boolean leaf = temp.isLeaf();
		
		temp.setName(menu.getName());
		temp.setLeaf(menu.isLeaf());
		temp.setIcon(menu.getIcon());
		temp.setUrl(menu.getUrl());
		temp.setLocked(menu.isLocked());
		temp.setActive(menu.isActive());
		
		java.util.Date now = new java.util.Date();
		String username = SecurityManager.getCurrentUser().getUsername();
		temp.setUpdateTime(now);
		temp.setUpdateUser(username);
		
		if(logger.isDebugEnabled()){
			logger.debug("Update Menu:"+ObjectUtils.objToString(menu));
		}

		if(menu.getParentCode() == null && temp.getParentCode() != null){//从子菜单置为顶级菜单
			logger.debug("Set to Root Node !");
			temp.setGrade(1);
			if(menu.getOrderIndex() < 1){
				temp.setOrderIndex( getMaxOrderIndex(null) + 1 );
			}else{
				temp.setOrderIndex(menu.getOrderIndex());
				refreshOrderIndex(menu.getParentCode(), menu.getOrderIndex());
			}
			temp.setParentCode(null);
			if(! leaf){
				//更新子菜单等级 
				updateGrade(temp.getCode(), 2);
			}
		}else if(menu.getParentCode() != null && ! menu.getParentCode().equals(temp.getParentCode())){//子菜单迁移父目录
			
			Menu parent = get(menu.getParentCode());
			logger.debug("Set to Child Node ! ParentName:"+parent.getName());
			
			if(parent.isLeaf()){//如果父菜单是叶子，那么现在当然是非叶子
				dao.bulkUpdate("update Menu m set m.leaf = ? where m.code = ?", new Object[]{false, parent.getCode()});
			}
			int newGrade = parent.getGrade() + 1;
			temp.setGrade(newGrade);
			if(menu.getOrderIndex() < 1){
				temp.setOrderIndex( getMaxOrderIndex(parent.getCode()) + 1 );
			}else{
				temp.setOrderIndex(menu.getOrderIndex());
				refreshOrderIndex(menu.getParentCode(), menu.getOrderIndex());
			}
			temp.setParentCode(menu.getParentCode());
			if(! leaf){
				updateGrade(temp.getCode(), newGrade+1);
			}
		}else{
			logger.debug("ParentCode is not change ! Grade:"+menu.getGrade());
			temp.setGrade(menu.getGrade());
			if(temp.getOrderIndex() != menu.getOrderIndex()){
				temp.setOrderIndex(menu.getOrderIndex());

				refreshOrderIndex(temp.getParentCode(), temp.getOrderIndex());
			}
		}
		
		onUpdate(temp);
		
		dao.update(temp);
		
		afterUpdate(old, temp);
		
		return temp;
	}
	
	private void updateGrade(String code, int g){
		List<Menu> subMenus = dao.find("from Menu m where m.parentCode = ?", new Object[]{code});
		if(subMenus != null && subMenus.size() > 0){
			String codes = "";
			for(int i=0; i<subMenus.size(); i++){
				Menu menu = subMenus.get(i);
				if(i>0)codes += ",";
				codes += "'"+menu.getCode()+"'";
				if(! menu.isLeaf()){
					updateGrade(menu.getCode(), menu.getGrade() + 1);
				}
			}
			dao.bulkUpdate("update Menu m set m.grade = "+g+" where m.code in ("+codes+")");
		}
	}

	/* (non-Javadoc)
	 * @see com.asc.sf.filter.IFilterResourceLoader#loadFilterResource()
	 */
	public Map<String, String> loadFilterResource() {
		String queryString = "select m from Menu m inner join fetch m.auth where m.filter = true";
		
		List<Menu> resources = dao.find(queryString);
		
		CollectionUtils.uniqueList(resources);
		
		Map<String, String> map = new LinkedHashMap<String, String>();
		
		for(int i=0; i<resources.size(); i++){
			Menu resource = resources.get(i);
			Authority auth = resource.getAuth();
			map.put(resource.getUrl(), auth.getCode());
		}
		
		return map;
	}

	public Menu get(String code) {
		return dao.get(Menu.class, code);
	}
	
	private int getMaxOrderIndex(String parentCode) {
		Integer max = null;
		if(parentCode == null){
			max = dao.queryForObject("select max(m.orderIndex) from Menu m where m.grade = 1");
		}else{
			max = dao.queryForObject("select max(m.orderIndex) from Menu m where m.parentCode = ?", parentCode);
		}
		if(max != null){
			return max;
		}
		return 0;
	}
	
	/*----- 发布事件  -----*/
	protected void onAdd(Menu menu){
		SecurityManager.publishEvent(new MenuEvent(menu, MenuEvent.ADD));
	}
	protected void onUpdate(Menu menu){
		SecurityManager.publishEvent(new MenuEvent(menu, MenuEvent.UPDATE));
	}
	protected void onDelete(Menu menu){
		SecurityManager.publishEvent(new MenuEvent(menu, MenuEvent.DELETE));
	}
	/*----- 记录日志 -----*/
	protected void afterAdd(Menu menu){
		LogHelper.actionLog(LangUtil.get("CreateMenu"), 
				LangUtil.get("MenuDetail", ObjectUtils.objToString(menu)));
	}
	protected void afterUpdate(Menu old, Menu menu){
		LogHelper.actionLog(LangUtil.get("UpdateMenu"), 
				LangUtil.get("UpdateMenuDetail", ObjectDifferenceChecker.check(old, menu)));
	}
	protected void afterDelete(Menu menu){
		LogHelper.actionLog(LangUtil.get("DeleteMenu"), 
				LangUtil.get("MenuDetail", ObjectUtils.objToString(menu)));
	}
}
